package com.tanhua.dubbo.es.api;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.dubbo.config.annotation.Service;
import com.tanhua.dubbo.server.api.UserLocationApi;
import com.tanhua.dubbo.server.pojo.UserLocation;
import com.tanhua.dubbo.server.vo.PageInfo;
import com.tanhua.dubbo.server.vo.UserLocationVo;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.GeoDistanceQueryBuilder;
import org.elasticsearch.search.sort.GeoDistanceSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.*;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 描述: 用户地理位置实现类
 * 作者: 张帆
 * 创建时间: 2021/7/8 15:21
 */
@Service(version = "1.0.0")
@Slf4j
public class UserLocationApiImpl implements UserLocationApi {
    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    /**
     * 初始化索引库
     *
     * @PostConstruct 该注解被用来修饰一个非静态的void（）方法。
     * 被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行，并且只会被服务器执行一次。
     * PostConstruct在构造函数之后执行，init（）方法之前执行。
     */
    @PostConstruct
    public void initIndex() {
        // 判断索引库是否存在，如果不存在，需要创建
        if (!this.elasticsearchTemplate.indexExists("tanhua")) {
            this.elasticsearchTemplate.createIndex(UserLocation.class);
        }

        // 判断表是否存在，如果不存在，需要创建
        if (!this.elasticsearchTemplate.typeExists("tanhua", "user_location")) {
            this.elasticsearchTemplate.putMapping(UserLocation.class);
        }
    }

    /**
     * 更新用户地理位置
     *
     * @param userId    用户id
     * @param longitude 经度
     * @param latitude  纬度
     * @param address   地址名称
     * @return 更新成功与否
     */
    @Override
    public Boolean updateUserLocation(Long userId, Double longitude, Double latitude, String address) {
        /* 分析：查询个人的地理位置数据，如果不存在，需要新增，如果是存在数据，更新数据 */

        try {
            /* 一、查询用户地理位置 */
            GetQuery getQuery = new GetQuery();
            getQuery.setId(String.valueOf(userId)); // 设置查询主键
            UserLocation userLocation = this.elasticsearchTemplate.queryForObject(getQuery, UserLocation.class);

            if (ObjectUtil.isEmpty(userLocation)) {
                /* 新增数据 */
                // 封装数据
                userLocation = new UserLocation();
                userLocation.setUserId(userId); // 用户id【主键】
                userLocation.setLocation(new GeoPoint(latitude, longitude)); // 经纬度
                userLocation.setAddress(address); // 位置描述
                userLocation.setCreated(System.currentTimeMillis()); // 创建时间
                userLocation.setUpdated(userLocation.getCreated()); // 更新时间
                userLocation.setLastUpdated(userLocation.getCreated()); // 上次更新时间

                // 将数据保存在ES中
                IndexQuery indexQuery = new IndexQueryBuilder().withObject(userLocation).build();
                this.elasticsearchTemplate.index(indexQuery);
            } else {
                /* 更新数据 */
                // 更新字段
                Map<String, Object> map = new HashMap<>();
                map.put("location", new GeoPoint(latitude, longitude)); // 经纬度
                map.put("address", address); // 位置描述
                map.put("updated", System.currentTimeMillis());  // 更新时间
                map.put("lastUpdated", userLocation.getUpdated()); // 上次更新时间

                // 更新请求
                UpdateRequest updateRequest = new UpdateRequest();
                updateRequest.doc(map);

                // 创建搜索模板
                UpdateQuery updateQuery = new UpdateQueryBuilder()
                        .withId(String.valueOf(userId)) // 主键id
                        .withClass(UserLocation.class)
                        .withUpdateRequest(updateRequest) // 更新请求
                        .build();

                // 更改数据
                this.elasticsearchTemplate.update(updateQuery);
            }

            return true;
        } catch (Exception e) {
            log.error("更新地理位置失败~ userId = " + userId + ", longitude = " + longitude +
                    ", latitude = " + latitude + ", address = " + address, e);
        }

        return false;
    }


    /**
     * 根据用户id查询用户所在位置
     *
     * @param userId 用户id
     * @return
     */
    @Override
    public UserLocationVo queryUserLocationByUserId(Long userId) {
        // 构建查询条件
        GetQuery getQuery = new GetQuery();
        getQuery.setId(String.valueOf(userId)); // 根据id查询

        // 查询数据
        UserLocation userLocation = this.elasticsearchTemplate.queryForObject(getQuery, UserLocation.class);

        if (ObjectUtil.isNotEmpty(userLocation)) {
            return UserLocationVo.format(userLocation);
        }
        return null;
    }


    /**
     * 根据位置搜索
     *
     * @param longitude 经度
     * @param latitude  纬度
     * @param distance  距离(米)
     * @param page      页数
     * @param pageSize  页面大小
     */
    @Override
    public PageInfo<UserLocationVo> queryUserFromLocation(Double longitude, Double latitude, Double distance, Integer page, Integer pageSize) {
        PageInfo<UserLocationVo> pageInfo = new PageInfo<>();
        pageInfo.setPageNum(page);
        pageInfo.setPageSize(pageSize);

        String fieldName = "location";

        /* 实现了SearchQuery接⼝，构造分⻚、排序 */
        NativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder();

        /* 分页 */
        PageRequest pageable = PageRequest.of(page - 1, pageSize);
        searchQueryBuilder.withPageable(pageable);

        /* 范围 */
        // 以⼀个点为中⼼，指定范围查询
        GeoDistanceQueryBuilder geoDistanceQueryBuilder = new GeoDistanceQueryBuilder(fieldName);
        // 中心点
        geoDistanceQueryBuilder.point(latitude, longitude);
        // 距离（画圆的半径）单位：公⾥ // TODO 这里为了效果，只除以100
        geoDistanceQueryBuilder.distance(distance / 100, DistanceUnit.KILOMETERS);

        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
        boolQueryBuilder.must(geoDistanceQueryBuilder);
        searchQueryBuilder.withQuery(boolQueryBuilder);

        /* 排序 */
        // 排序，由近到远排序
        GeoDistanceSortBuilder geoDistanceSortBuilder = new GeoDistanceSortBuilder(fieldName, latitude, longitude);
        geoDistanceSortBuilder.order(SortOrder.ASC); // 正序
        geoDistanceSortBuilder.unit(DistanceUnit.KILOMETERS); // 设置单位 公里
        searchQueryBuilder.withSort(geoDistanceSortBuilder);

        /* 查询数据 */
        AggregatedPage<UserLocation> aggregatedPage = this.elasticsearchTemplate.queryForPage(searchQueryBuilder.build(), UserLocation.class);

        List<UserLocation> userLocationList = aggregatedPage.getContent();
        if (CollUtil.isEmpty(userLocationList)) {
            return pageInfo;
        }

        // 封装数据并返回
        pageInfo.setRecords(UserLocationVo.formatToList(userLocationList));
        return pageInfo;
    }
}
